package Execute.MiniSQL.module;

import Execute.MiniSQL.SqlResult;
import Execute.MiniSQL.TableManager;
import Execute.MiniSQL.Transformer;

import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

public class Select {
    public String tableName;        // 表名
    public String condition;        // 条件，"colA_,_colB from XXX where colC_?_x_,_colD_?_x order by colC desc"
    public Map<String, String> map; // 存储表结构 <列名称，数据类型>
    public Map<String, String> userMap; // 存储判别条件 <列名称， 操作符_值>
    public LinkedList<String> list_showCol;    // 存储搜索条件（用户要查看的列）
    public LinkedList<String> list_colMsg;       // 存储列名称（顺序）信息
    public int singleLen;           // 单个数据的长度（一行
    public SqlResult sqlResult;
    public LinkedList<Integer> list_stringLen;  // 字符型数据占长度
    public boolean showAll;
    public boolean need_order;

    public Select(String tableName, String sql) {
        this.tableName = tableName;
        this.condition = sql;
        this.map = new LinkedHashMap<>();
        this.userMap = new LinkedHashMap<>();
        this.list_showCol = new LinkedList<>();
        this.list_colMsg = new LinkedList<>();
        this.list_stringLen = new LinkedList<>();
        this.singleLen = 0;
        if(condition.toLowerCase().contains("where")){
            this.showAll = false;
        } else {
            this.showAll = true;
        }
        if(condition.toLowerCase().contains("order")){
            this.need_order = true;
        } else {
            this.need_order = false;
        }

        // 执行查询操作
        if(check()) {
            selectFunc();
        }
    }

    /*
     * 该方法负责进行预处理。
     * 1、判断给出的条件 列名称、数据类型是否可用
     * 2、将相应的信息存入对应的变量（map、userMap、list_searchcd、list_stringLen、singLen...)
     * */
    public boolean check() {
        // 先获取表结构
        String tableStruct = TableManager.preDesc(tableName);   // tableStruct = "Student id int Height decimal name varchar(20)"
        String[] ts = tableStruct.split(" ");

        // 更新map
        // 计算表中一个数据的长度  int 4个字节 double 8个字节 字符varchar(a) 3a个字节
        for(int i = 1; i < ts.length; i += 2){
            // 将列名称和数据类型存入map
            map.put(ts[i], ts[i+1]);
            list_colMsg.add(ts[i]);
            if (ts[i + 1].equalsIgnoreCase("int")) {
                // 整型占4个字节
                singleLen += 4;
            } else if (ts[i + 1].equalsIgnoreCase("decimal")) {
                // double占8个字节
                singleLen += 8;
            } else {
                // varchar(a) 占3a个字节
                int l = TableManager.getVarchar(ts[i + 1]);
                l = 3 * l;
                list_stringLen.add(l);  // 存入链表
                singleLen += l;
            }
        }

        // 提取条件 更新list_showCol
        String user_see;
        try {
            user_see = condition.substring(0, condition.toLowerCase().indexOf(" from")); // user_see = "colA_,_colB"
        } catch (Exception e1) {
            System.out.println("请检查您的输入，或许您想输入的是 `from` ?");
            return false;
        }
        // list_showCol 提取用户要查看的信息
        String[] showCol = user_see.split(",");         // showCol = "colA_" "_colB"
        for(String s : showCol) {
            s = s.trim();       //
            s = s.replaceAll("\\s+", "");   // s = "colA" "colB"

            if(!s.equalsIgnoreCase("*")) {
                // 列名是否存在
                if (map.get(s) == null) {
                    System.out.println(s + "：您输入的列名在不存在！");
                    this.sqlResult = new SqlResult(s + "：您输入的列名在不存在！", null);
                    return false;
                }
            }

            // 存在，可以执行搜索
            // 将需要显示的列名称存入list_showCol链表
            list_showCol.add(s);
        }

        // 如果存在where条件的话
        if(!showAll) {
            // 提取条件 更新userMap
            String search_str;
            if (!condition.toLowerCase().contains("order"))
                search_str = condition.substring(condition.indexOf("where ") + 6);
            else {
                search_str = condition.substring(condition.indexOf("where ") + 6, condition.indexOf(" order"));
            }
            String[] search = search_str.split(",");    // "colC_?_x_,_colD_?_x" -> "colC_?_x_" "_colD_?_x"
            for (String s : search) {
                s = s.trim();
                s = s.replaceAll("\\s+", "");    // s = "colA?x"
                // 提取操作符
                String oper = null;
                if (s.contains(">=")) oper = ">=";
                else if (s.contains("<=")) oper = "<=";
                else if (s.contains("<>")) oper = "<>";
                else if (s.contains(">")) oper = ">";
                else if (s.contains("<")) oper = "<";
                else if (s.contains("=")) oper = "=";

                // 提取列名
                String colName = s.substring(0, s.indexOf(oper));       // colName = "colA"
                // 提取值
                String value;
                if(oper.length() == 1)
                    value = s.substring(s.indexOf(oper) + 1);          // value = "x"
                else
                    value = s.substring(s.indexOf(oper) + 2);

                // 列名是否存在
                if (map.get(colName) == null) {
                    System.out.println(colName + "：您输入的列名在不存在！");
                    this.sqlResult = new SqlResult(colName + "：您输入的列名在不存在！", null);
                    return false;
                }

                // 值类型输入是否正确
                String type = map.get(colName);
                switch (type) {
                    case "int":
                        try {
                            int linshi = Integer.parseInt(value);
                        } catch (Exception e) {
                            System.out.println(s + ": 输入需要为一个整数。");

                            return false;
                        }
                        break;
                    case "decimal":
                        try {
                            double linshi = Double.parseDouble(value);
                        } catch (Exception e) {
                            System.out.println(s + ": 输入需要为一个小数。");
                            return false;
                        }
                        break;
                    default:
                        if ((value.startsWith("'") & !value.endsWith("'")) | (!value.startsWith("'") & value.endsWith("'"))) {
                            System.out.println(s + ": 字符型数据您可以不输入分号或输入2个分号。");
                            return false;
                        }
                        break;
                }

                // 表名、列名存在，可以执行搜索
                // 将检索的信息存入userMap
                userMap.put(colName, oper + " " + value);
            }
        }

        return true;
    }

    public void selectFunc() {
        String path = TableManager.DATA_PATH + "/" + tableName + ".midb";
        try {
            RandomAccessFile raf = new RandomAccessFile(path, "r");
            long LEN = raf.length();        // 文本总长度
            long nowPos = 0;             // 当前指针位置
            // 一次读取长度
            byte[] bytes_int = new byte[4];
            byte[] bytes_double = new byte[8];
            byte[] bytes_varchar;

            if(showAll) {
                // 不存在where子句
                if(list_showCol.get(0).equalsIgnoreCase("*")) {
                    // 不存在检索条件 select * from XXX
                    // 全部显示
                    int num = 0;
                    // 输出列名
                    boolean f1 = true;
                    for(Map.Entry<String, String> entry : map.entrySet()) {
                        if(f1) {
                            System.out.print(entry.getKey());
                            f1 = false;
                        } else {
                            System.out.print(", " + entry.getKey());
                        }
                    }
                    System.out.print("\n");

                    // 输出所有数据
                    Object num_int = null, num_dou = null, s = null;
                    while(nowPos < LEN) {
                        // 读取一行数据的操作
                        f1 = true;
                        int k = 0;      // list_stringLen的下标
                        // 遍历map，得到各个列的数据类型
                        for(Map.Entry<String, String> entry : map.entrySet()) {
                            if (entry.getValue().equalsIgnoreCase("int")) {

                                raf.read(bytes_int);
                                nowPos += 4;
                                num_int = Transformer.bytesToInt(bytes_int);
                                if(f1) {
                                    System.out.print(num_int);
                                    f1 = false;
                                } else {
                                    System.out.print(", " + num_int);
                                }

                            } else if (entry.getValue().equalsIgnoreCase("decimal")) {

                                raf.read(bytes_double);
                                nowPos += 8;
                                num_dou = Transformer.bytesToDouble(bytes_double);
                                if(f1) {
                                    System.out.print(num_dou);
                                    f1 = false;
                                } else {
                                    System.out.print(", " + num_dou);
                                }

                            } else {

                                bytes_varchar = new byte[list_stringLen.get(k)];
                                raf.read(bytes_varchar);
                                nowPos += list_stringLen.get(k);
                                k++;
                                s = new String(bytes_varchar, StandardCharsets.UTF_8).trim();
                                if(f1) {
                                    System.out.print(s);
                                    f1 = false;
                                } else {
                                    System.out.print(", " + s);
                                }

                            }
                        }
                        // 下一行
                        System.out.print("\n");
                        raf.skipBytes(2);
                        nowPos += 2;
                        num++;
                    }
                    System.out.println("共有 " + num + " 条记录");
                   // select * from XXX 结束
                } else {
                    // 不含where子句，不全部显示（不含*）
                    // select colA,colB from XXX

                    int num = 0;

                    // 输出列名
                    boolean f1 = true;
                    for (String s : list_showCol) {
                        if(f1) {
                            System.out.print(s);
                            f1 = false;
                        } else {
                            System.out.print(", " + s);
                        }
                    }
                    System.out.print("\n");

                    // 输出对应列的数据
                    Object num_int = null, num_dou = null, s = null;
                    while(nowPos < LEN) {
                        // 读取一行数据的操作
                        f1 = true;
                        int k = 0;      // list_stringLen的下标
                        // 遍历map，得到各个列的数据类型
                        for(Map.Entry<String, String> entry : map.entrySet()) {
                            if (entry.getValue().equalsIgnoreCase("int")) {
                                // 是要显示的列
                                boolean f2 = wheExists(entry.getKey());
                                if(f2) {
                                    raf.read(bytes_int);
                                    num_int = Transformer.bytesToInt(bytes_int);
                                    if (f1) {
                                        System.out.print(num_int);
                                        f1 = false;
                                    } else {
                                        System.out.print(", " + num_int);
                                    }
                                } else {
                                    raf.skipBytes(4);
                                }
                                nowPos += 4;
                            } else if (entry.getValue().equalsIgnoreCase("decimal")) {
                                boolean f2 = wheExists(entry.getKey());
                                if(f2) {
                                    raf.read(bytes_double);
                                    num_dou = Transformer.bytesToDouble(bytes_double);
                                    if (f1) {
                                        System.out.print(num_dou);
                                        f1 = false;
                                    } else {
                                        System.out.print(", " + num_dou);
                                    }
                                } else {
                                    raf.skipBytes(8);
                                }
                                nowPos += 8;
                            } else {
                                boolean f2 = wheExists(entry.getKey());
                                if(f2) {
                                    bytes_varchar = new byte[list_stringLen.get(k)];
                                    raf.read(bytes_varchar);
                                    s = new String(bytes_varchar, StandardCharsets.UTF_8).trim();
                                    if(f1) {
                                        System.out.print(s);
                                        f1 = false;
                                    } else {
                                        System.out.print(", " + s);
                                    }
                                } else {
                                    raf.skipBytes(list_stringLen.get(k));
                                }

                                nowPos += list_stringLen.get(k);
                                k++;
                            }

                        }
                        // 下一行
                        System.out.print("\n");
                        num++;
                        raf.skipBytes(2);
                        nowPos += 2;
                    }
                    System.out.println("共有 " + num + " 条记录");
                }
            } else {

                // 包含where子句
                String orderColName = null;
                boolean flagAscend = false;
                if(need_order) {
                    // 没有asc desc
                    if(!condition.toLowerCase().contains("asc") & !condition.toLowerCase().contains("desc"))
                        orderColName = condition.substring(condition.indexOf("by ")+3);
                    else if (condition.toLowerCase().contains("asc")) {
                        orderColName = condition.substring(condition.indexOf("by ")+3, condition.indexOf("asc")-1);
                        flagAscend = true;
                    } else {
                        orderColName = condition.substring(condition.indexOf("by ")+3, condition.indexOf("desc")-1);
                    }
                }

                boolean[] isSearch = new boolean[userMap.size()];
                int pos = 0;        // 当前 isSearch[] 下标
                int searchNum = 0;  // 查找到数据的数量,同时可作为二维数组objects的行下标、byObjects的下标

                Object[][] objects = new Object[(int)(LEN / (singleLen+2))][];
                Object[] byObjects = null;
                if(need_order)
                    byObjects = new Object[(int)(LEN / (singleLen+2))];

                // 开始读文件
                while(nowPos < LEN) {
                    Object[] linshi = new Object[map.size()];   // 临时存放行数据，符合要求再加入objects中
                    int j = 0;  // 读取文件该行的第j+1列，，二维数组objects的列下标
                    // 每个数据开始前将 isSearch 数组初始为false, pos、k初始为0
                    Arrays.fill(isSearch, Boolean.FALSE);
                    pos = 0;
                    int k = 0;      // list_stringLen的下标

                    // 读取一行数据的操作
                    // 遍历map，得到各个列的数据类型
                    for(Map.Entry<String, String> entry : map.entrySet()) {
                        if (entry.getValue().equalsIgnoreCase("int")) {

                            raf.read(bytes_int);
                            nowPos += 4;
                            linshi[j] = Transformer.bytesToInt(bytes_int);
                            // 该项是否是检索判断的条件，是的话判断是否满足检索条件
                            if(whetherSearch(entry.getKey(), linshi[j])) {
                                // 该列是检索的列，且满足条件
                                isSearch[pos] = true;
                                pos++;
                            }
                            j++;

                            if(orderColName != null) {
                                // 需要排序
                                if(entry.getKey().equalsIgnoreCase(orderColName)) {
                                    // 该列是依据排序的列
                                    byObjects[searchNum] = linshi[j-1];
                                }
                            }

                        } else if (entry.getValue().equalsIgnoreCase("decimal")) {

                            raf.read(bytes_double);
                            nowPos += 8;
                            linshi[j] = Transformer.bytesToDouble(bytes_double);

                            // 该项是否是判断删除的条件，是的话判断是否满足删除条件
                            if(whetherSearch(entry.getKey(), linshi[j])) {
                                // 该列是检索的列，且满足条件
                                isSearch[pos] = true;
                                pos++;
                            }
                            j++;

                            if(orderColName != null) {
                                // 需要排序
                                if(entry.getKey().equalsIgnoreCase(orderColName)) {
                                    // 该列是依据排序的列
                                    byObjects[searchNum] = linshi[j-1];
                                }
                            }


                        } else {

                            bytes_varchar = new byte[list_stringLen.get(k)];
                            raf.read(bytes_varchar);
                            nowPos += list_stringLen.get(k);
                            k++;
                            linshi[j] = new String(bytes_varchar, StandardCharsets.UTF_8).trim();
                            // 该项是否是判断删除的条件，是的话判断是否满足删除条件
                            if(whetherSearch(entry.getKey(), linshi[j])) {
                                // 该列是检索的列，且满足条件
                                isSearch[pos] = true;
                                pos++;
                            }
                            j++;
                            if(orderColName != null) {
                                // 需要排序
                                if(entry.getKey().equalsIgnoreCase(orderColName)) {
                                    // 该列是依据排序的列
                                    byObjects[searchNum] = linshi[j-1];
                                }
                            }

                        }
                    }

                    // 判断是否要检索，即检查isSearch数组值是否全为真
                    // 因为前面是顺序赋值的，所以检查最后一个值是否为true即可
                    if (isSearch[isSearch.length - 1]) {
                        objects[searchNum] = linshi;    // 添加进objecs中
                        searchNum++;
                    }

                    // 下一行
                    raf.skipBytes(2);   // 跳过换行符
                    nowPos += 2;

                }

                Object[][] objs = new Object[searchNum][];
                Object[] byobjs = new Object[searchNum];
                for(int i1 = 0; i1 < searchNum; i1++){
                    objs[i1] = objects[i1];
                    if(need_order) byobjs[i1] = byObjects[i1];
                }
                objs = sortedDisplay(objs, byObjects, flagAscend);

                if(list_showCol.get(0).equalsIgnoreCase("*")) {
                    // 输出列名
                    boolean f1 = true;
                    for(Map.Entry<String, String> entry : map.entrySet()) {
                        if(f1) {
                            System.out.print(entry.getKey());
                            f1 = false;
                        } else {
                            System.out.print(", " + entry.getKey());
                        }
                    }
                    System.out.print("\n");

                    // 输出所有数据
                    for (int i1 = 0; i1 < searchNum; i1++) {
                        boolean f11 = true;
                        for (Object o : objs[i1]) {
                            if(f11) {
                                System.out.print(o);
                                f11 = false;
                            } else {
                                System.out.print(", " + o);
                            }
                        }
                        System.out.print("\n");
                    }
                    System.out.println("共有 " + searchNum + " 条记录");
                } else {

                    // 输出列名
                    boolean f1 = true;
                    for (String s : list_showCol) {
                        if(f1) {
                            System.out.print(s);
                            f1 = false;
                        } else {
                            System.out.print(", " + s);
                        }
                    }
                    System.out.print("\n");

                    // 输出对应列的数据
                    for (int i1 = 0; i1 < searchNum; i1++) {
                        boolean f11 = true;
                        //for (Object o : objs[i1]) {
                        for (int i2 = 0; i2 < objs[i1].length; i2++) {
                            if(wheExists(list_colMsg.get(i2))) {
                                if (f11) {
                                    System.out.print(objs[i1][i2]);
                                    f11 = false;
                                } else {
                                    System.out.print(", " + objs[i1][i2]);
                                }
                            }
                        }
                        System.out.print("\n");
                    }
                    System.out.println("共有 " + searchNum + " 条记录");
                }

            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * 判断该值是否满足修改条件
     * 文件中该数据的 colName 列名称 value 列值
     * userMap 用户输入的删除条件 <列名称，操作符_值>
     * */
    public boolean whetherSearch(String colName, Object value) {
        for(Map.Entry<String, String> entry : userMap.entrySet()) {
            // 列名是否在判断条件中
            if (entry.getKey().equalsIgnoreCase(colName)) {
                // 存在，判断是否符合条件
                String str = entry.getValue();      // str = "oper_value"
                // 提取操作符和值
                String oper = str.substring(0, str.indexOf(" "));
                String val = str.substring(str.indexOf(" ")+1);
                // 后缀1是数据，后缀2是判断条件
                int int1 = 0,int2 = 0;
                double dou1 = 0,dou2 = 0;
                String s1 = null,sign;
                if (value instanceof Integer) {
                    int1 = (int)value;
                    int2 = Integer.parseInt(val);
                    sign = "int";
                } else if (value instanceof Double) {
                    dou1 = (double) value;
                    dou2 = Double.parseDouble(val);
                    sign = "double";
                } else {
                    s1 = (String) value;
                    if(val.startsWith("'") & val.endsWith("'"))
                        val = val.substring(1, val.length()-1);     // 去除引号
                    sign = "str";
                }

                switch (oper){
                    case ">":

                        if (sign.equals("int")) return int1 > int2;
                        else if(sign.equals("double")) return dou1 > dou2;
                        else return s1.compareTo(val) > 0;

                    case "<":

                        if (sign.equals("int")) return int1 < int2;
                        else if(sign.equals("double")) return dou1 < dou2;
                        else return s1.compareTo(val) < 0;

                    case "=":

                        if (sign.equals("int")) return int1 == int2;
                        else if(sign.equals("double")) return dou1 == dou2;
                        else return s1.compareTo(val) == 0;

                    case ">=":

                        if (sign.equals("int")) return int1 >= int2;
                        else if(sign.equals("double")) return dou1 >= dou2;
                        else return s1.compareTo(val) >= 0;

                    case "<=":

                        if (sign.equals("int")) return int1 <= int2;
                        else if(sign.equals("double")) return dou1 <= dou2;
                        else return s1.compareTo(val) <= 0;

                    case "<>":

                        if (sign.equals("int")) return int1 != int2;
                        else if(sign.equals("double")) return dou1 != dou2;
                        else return s1.compareTo(val) != 0;

                    default:
                        System.out.println("修改失败，请重试！");
                        this.sqlResult = new SqlResult("修改失败，请重试！", null);
                        return false;
                }

            }
        }
        // 该列不是要判断的列
        return false;
    }

    /*
    * 该列是否要显示
    * 显示 true
    * 不显示 false
    * */
    public boolean wheExists(String colName) {
        for (String s : list_showCol) {
            if(s.equalsIgnoreCase(colName)) {
                return true;
            }
        }
        return false;
    }


    // objects: 按照选定的行和列返回的要显示的内容；
    // byObjects：排序依据的列，这个列的内容要么是Integer，要么是Double，要么是String，肯定是三者之一。
    // byOjbects 有可能已经在objects中，是其中的某一列，但为了排序，要单独拿出来。
    // flagAscend：如果排序要求是升序的，则这个值是true，否则是false。如果不要求排序，这个值随意，没什么用，默认是false
    public static Object[][] sortedDisplay(Object[][] objects, Object[] byObjects, boolean flagAscend) {
        // 如果没有排序要求，则直接返回获取的内容：
        if (byObjects == null)
            return objects;
        // 如果一行都没有选中，则返回一个非null的数组。
        if (objects.length == 0)
            return new Object[0][];
        char type = 'i';
        // 这里可以最小限度地使用反射，也可以通过SQL语句判断，而不需要反射：
        if (byObjects[0] instanceof Double)
            type = 'd';
        if (byObjects[0] instanceof String)
            type = 's';
        // 内部类要用到这个类型结果，它必须是final的，或本质上是final的，因此要重新赋值一次：
        char typeFinal = type;
        // 下面要展现内部类的魅力了：
        class LineAndBy implements Comparable<LineAndBy> {
            Object[] line;
            Object byObject;
            LineAndBy(Object[] line, Object byObject) {
                this.line = line;
                this.byObject = byObject;
            }
            public int compareTo(LineAndBy that) {
                if (typeFinal == 'i') {
                    Integer thisInteger = (Integer) this.byObject;
                    Integer thatInteger = (Integer) that.byObject;
                    int compareResult = thisInteger.compareTo(thatInteger);
                    if (flagAscend)
                        return compareResult;
                    else
                        return -compareResult;
                }
                else if (typeFinal == 'd') {
                    Double thisDouble = (Double) this.byObject;
                    Double thatDouble = (Double) that.byObject;
                    int compareResult = thisDouble.compareTo(thatDouble);
                    if (flagAscend)
                        return compareResult;
                    else
                        return -compareResult;
                }
                else {
                    String thisString = (String) this.byObject;
                    String thatString = (String) that.byObject;
                    int compareResult = thisString.compareTo(thatString);
                    if (flagAscend)
                        return compareResult;
                    else
                        return -compareResult;
                }
            }
        }
        int amount = objects.length;
        int columns = objects[0].length;
        LineAndBy[] lineAndBys = new LineAndBy[amount];
        for (int i=0; i<amount; i++)
            lineAndBys[i] = new LineAndBy(objects[i], byObjects[i]);
        Arrays.sort(lineAndBys);
        Object[][] objectsToDisplay = new Object[amount][];
        for (int i=0; i<amount; i++)
            objectsToDisplay[i] = lineAndBys[i].line;
        return objectsToDisplay;
    }

}
